package heven.holt.library.http.download.iml

import android.net.Uri
import dagger.hilt.android.EntryPointAccessors
import heven.holt.library.base.baseApplication
import heven.holt.library.database.dao.DownloadDao
import heven.holt.library.di.DownloadProviderEntryPoint
import heven.holt.library.extensions.copyTo
import heven.holt.library.http.download.*
import heven.holt.library.model.DownloadInfo
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import okhttp3.ResponseBody
import timber.log.Timber
import java.io.File
import java.io.InputStream
import java.io.RandomAccessFile
import java.math.RoundingMode
import java.text.DecimalFormat

private val downloadScope = CoroutineScope(CoroutineName("download") + Dispatchers.Main)

class DownloadControllerIml(
    private val url: String,
    private val downloadDao: DownloadDao
) : DownloadController {

    private val flow = flow {
        val downloadInfo = getCacheDownloadInfo(url)
        download(downloadInfo)
    }

    private var downloadJob: Job? = null

    private var error: DOWNLOAD_ERROR? = null
    private var progress: DOWNLOAD_PROGRESS? = null
    private var success: DOWNLOAD_SUCCESS? = null

    override fun progress(progress: DOWNLOAD_PROGRESS) {
        this.progress = progress
    }

    override fun error(error: DOWNLOAD_ERROR) {
        this.error = error
    }

    override fun success(success: DOWNLOAD_SUCCESS) {
        this.success = success
    }


    override fun start() {
        if (downloadJob != null) {
            Timber.e("the download progress is working now.")
            return
        }
        downloadJob = flow.flowOn(Dispatchers.IO)
            .onEach(::assembleDownloadStatus)
            .cancellable()
            .launchIn(downloadScope)
    }

    override fun stop() {
        downloadJob?.cancel()
        downloadJob = null
    }

    private fun getCacheDownloadInfo(url: String): DownloadInfo {
        return downloadDao.getDownloadInfoByUrl(url) ?: DownloadInfo(url)
    }

    private fun saveCacheDownloadInfo(downloadInfo: DownloadInfo) {
        downloadDao.insertOrUpdate(downloadInfo)
    }

    private fun assembleDownloadStatus(it: DownloadStatus) {
        when (it) {
            is DownloadStatus.DownloadProgress -> {
                progress?.invoke(it.currentLength, it.totalLength, it.progress)
            }
            is DownloadStatus.DownloadError -> {
                stop()
                error?.invoke(it.t)
            }
            is DownloadStatus.DownloadSuccess -> {
                stop()
                success?.invoke(it.uri)
            }
        }
    }

    private fun createCacheFile(fileName: String): File? {
        return if (fileName.isEmpty()) null else File(baseApplication.cacheDir, fileName)
    }

    suspend fun FlowCollector<DownloadStatus>.download(downloadInfo: DownloadInfo) =
        kotlin.runCatching {
            val file = createCacheFile(downloadInfo.fileName)

            if (downloadInfo.completeDownload) {//has download
                return@runCatching Uri.fromFile(file)
            }

            val rangeLength = downloadInfo.length
            val response = requestDownloadServer(downloadInfo.url, rangeLength)

            if (downloadInfo.totalLength == 0L) {
                downloadInfo.totalLength = response.contentLength()
            }
            val totalLength = downloadInfo.totalLength

            val inputStream: InputStream = response.byteStream()

            val randomAccessFile = RandomAccessFile(file, "rw")

            val decimalFormat = DecimalFormat("#.00")
            decimalFormat.roundingMode = RoundingMode.FLOOR
            var currentProgress = 0f

            randomAccessFile.seek(rangeLength)
            inputStream.copyTo(randomAccessFile, { downloadSize ->
                val currentLength = downloadSize + rangeLength
                downloadInfo.length = currentLength
                saveCacheDownloadInfo(downloadInfo)
                val progress = decimalFormat.format(currentLength.toFloat() / totalLength).toFloat()
                if (currentProgress != progress) {
                    currentProgress = progress
                    emit(
                        DownloadStatus.DownloadProgress(
                            currentLength,
                            totalLength,
                            currentProgress
                        )
                    )
                }
            })

            inputStream.close()
            randomAccessFile.close()
            return@runCatching Uri.fromFile(file)
        }.onFailure {
            emit(DownloadStatus.DownloadError(it))
        }.onSuccess {
            emit(DownloadStatus.DownloadSuccess(it))
        }

    /**
     * 请求下载网络服务
     */
    private suspend fun requestDownloadServer(
        url: String,
        rangeLength: Long
    ): ResponseBody {
        val hiltEntryPoint =
            EntryPointAccessors.fromApplication(
                baseApplication,
                DownloadProviderEntryPoint::class.java
            )

        val downloadServer = hiltEntryPoint.provideDownload()
        return downloadServer.download(url, "bytes=$rangeLength-")
    }
}